/**
 * Created by ckto1212@gmail.com
 */
package com.welbell.android.test.camera;

import java.nio.ByteBuffer;

import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.util.Log;

/**
 * @author wangzong
 */
public class H_264CameraVideoEncoder {

	private Sender sender;

	private MediaCodec encoder;

	// size of a frame, in pixels
	private int mWidth = -1;
	private int mHeight = -1;
	// bit rate, in bits per second
	private int mBitRate = -1;
	// the support colorspace
	private int colorFormat = -1;

	private MediaFormat format = null;

	private boolean isEncode = false;

	public H_264CameraVideoEncoder(Sender sender, int width, int height, int bitRate) {
		this.sender = sender;
		this.mWidth = width;
		this.mHeight = height;
		this.mBitRate = bitRate;
	}

	public void startEncoderAndSend() {
		MediaCodecInfo codecInfo = selectCodec("video/avc");
		if (codecInfo == null) {
			log("Unable to find an appropriate codec for video/avc");
		} else {
			colorFormat = selectColorFormat(codecInfo, "video/avc");
		}
		format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight);
		format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
		format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
		format.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
		format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
		encoder = MediaCodec.createByCodecName(codecInfo.getName());
		encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
		if (sender != null)
			sender.start();
		encoder.start();
		isEncode = true;
	}

	private int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) {
		MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
		for (int i = 0; i < capabilities.colorFormats.length; i++) {
			int colorFormat = capabilities.colorFormats[i];
			if (isRecognizedFormat(colorFormat)) {
				return colorFormat;
			}
		}
		log("couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
		return 0; // not reached
	}

	/**
	 * Returns true if this is a color format that this test code understands
	 * (i.e. we know how to read and generate frames in this format).
	 */
	private boolean isRecognizedFormat(int colorFormat2) {
		switch (colorFormat2) {
		case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
		case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
		case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
		case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
		case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
			return true;
		}
		return false;
	}

	private MediaCodecInfo selectCodec(String string) {
		int numCodecs = MediaCodecList.getCodecCount();
		for (int i = 0; i < numCodecs; i++) {
			MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
			if (!codecInfo.isEncoder()) {
				continue;
			}
			String[] types = codecInfo.getSupportedTypes();
			for (int j = 0; j < types.length; j++) {
				if (types[j].equalsIgnoreCase(string)) {
					return codecInfo;
				}
			}
		}
		return null;
	}

	public void stopEncoderAndSend() {
		isEncode = false;
		if (sender != null)
			sender.stop();
	}

	public void encodeAndSend(byte[] originalData) {
//		log("    ----    encodeAndSend    isEncode:" + isEncode);
		if (isEncode) {
			// if (sender != null) {
			ByteBuffer[] inputBuffers = encoder.getInputBuffers();
			ByteBuffer[] outputBuffers = encoder.getOutputBuffers();
			int inputBufferIndex = encoder.dequeueInputBuffer(10000);
			if (inputBufferIndex >= 0) {
				ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
				if (inputBuffer.capacity() >= originalData.length) {
					inputBuffer.clear();
					inputBuffer.put(originalData, 0, originalData.length);
					encoder.queueInputBuffer(inputBufferIndex, 0, originalData.length, 0, 0);
				} else {
					Log.e("H_264CameraVideoEncoder", "inputBuffer.capacity():" + inputBuffer.capacity() + "< buf.length:" + originalData.length);
				}
			}
			MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
			int outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0);

			log("    ----    outputBufferIndex:" + outputBufferIndex);

			while (outputBufferIndex >= 0) {
				ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
				outputBuffer.position(bufferInfo.offset);
				outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
				byte[] data = new byte[bufferInfo.size];
				outputBuffer.get(data);
				outputBuffer.position(bufferInfo.offset);

				log("encoded data length:" + data.length + " " + data[0] + data[1] + data[2] + data[3] + " " + data[4]);

				if (sender != null) {
					sender.send(data);
				}
				encoder.releaseOutputBuffer(outputBufferIndex, false);
				outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0);
			}
			// }
		}
	}

	public void release() {
		if (encoder != null) {
			encoder.stop();
			encoder.release();
		}
		if (sender != null) {
			sender.stop();
			sender.release();
		}
	}

	private void log(String _log) {
		Log.v("H_264CameraVideoEncoder", "    ----    " + _log);
	}

}
